/*
 * Texture.h
 *
 *  Created on: Jan 1, 2011
 *      Author: burek
 */

#ifndef TEXTURE_H_
#define TEXTURE_H_

#include "VecRGB.h"
#include "Ray.h"
#include <cstdlib>
#include <cstdio>

using namespace RayUtils;

struct Texture
{
	VecRGB *pix;
	int width, height;
	Texture *nextMip;
	float mipScale;

	Texture(int w, int h)
	{
		width = w; height = h;
		pix = new VecRGB[w * h];
		nextMip = NULL;
		mipScale = 1.0f;
	}

	inline Texture operator = ( const Texture& A )
	{
		width = A.width;
		height = A.height;
		pix = A.pix;
		nextMip = A.nextMip;
		mipScale = A.mipScale;
	}

	inline VecRGB& getPixel(int x, int y) const
	{ x %= width; y %= height; return pix[y * width + x]; }

	inline void setPixel(int x, int y, VecRGB p)
	{ x %= width; y %= height; pix[y * width + x] = p; }

	inline VecRGB sampleBil(float u, float v) const
	{
		float uW = u * width, vH = v * height;
		int x1 = (int)uW % width;
		int y1 = (int)vH % height;
		if(x1 < 0) x1 += width;
		if(y1 < 0) y1 += height;
		int x2 = (x1 + 1) % width;
		int y2 = (y1 + 1) % height;
		float remU = uW - (int)uW;
		float remV = vH - (int)vH;
		if(remU < 0.0f) remU += 1.0f;
		if(remV < 0.0f) remV += 1.0f;


		float R = lerp(lerp(pix[y1*width+x1].R,pix[y1*width+x2].R,remU), lerp(pix[y2*width+x1].R,pix[y2*width+x2].R,remU), remV);
		float G = lerp(lerp(pix[y1*width+x1].G,pix[y1*width+x2].G,remU), lerp(pix[y2*width+x1].G,pix[y2*width+x2].G,remU), remV);
		float B = lerp(lerp(pix[y1*width+x1].B,pix[y1*width+x2].B,remU), lerp(pix[y2*width+x1].B,pix[y2*width+x2].B,remU), remV);

		//VecRGB upper = lerp(getPixel(x1,y1), getPixel(x2, y1), remU);
		//VecRGB lower = lerp(getPixel(x1,y2), getPixel(x2, y2), remU);
		//printf("%2.3f %2.3f %2.3f\n", R, G, B);
		return VecRGB(R,G,B);
		//printf("%2.3f %2.3f\n", remU, remV);
		//return lerp(upper, lower, remV);
	}

	inline float sampleBilGS(float u, float v) const
	{
		int x1 = static_cast<int>(u); // % width;
		int y1 = static_cast<int>(v); // % height;
		int x2 = (x1 + 1); // % width;
		int y2 = (y1 + 1); // % height;
		float remU = u - x1;
		float remV = v - y1;


		return lerp(
				lerp(pix[y1*width+x1].R,pix[y1*width+x2].R,remU),
				lerp(pix[y2*width+x1].R,pix[y2*width+x2].R,remU),
				remV);
		//float G = lerp(lerp(pix[y1*width+x1].G,pix[y1*width+x2].G,remU), lerp(pix[y2*width+x1].G,pix[y2*width+x2].G,remU), remV);
		//float B = lerp(lerp(pix[y1*width+x1].B,pix[y1*width+x2].B,remU), lerp(pix[y2*width+x1].B,pix[y2*width+x2].B,remU), remV);

		//VecRGB upper = lerp(getPixel(x1,y1), getPixel(x2, y1), remU);
		//VecRGB lower = lerp(getPixel(x1,y2), getPixel(x2, y2), remU);
		//printf("%2.3f %2.3f %2.3f\n", R, G, B);
		//return VecRGB(R,G,B);
		//printf("%2.3f %2.3f\n", remU, remV);
		//return lerp(upper, lower, remV);
	}

	VecRGB sampleTril(float u, float v, float sampSize) const
	{
		if(sampSize > mipScale * 2.0f)
		{
			if(nextMip != NULL)
			{
				return nextMip->sampleTril(u, v, sampSize);
			}
		}

		VecRGB mySamp = sampleBil(u, v);
		if(nextMip != NULL)
		{
			return lerp(mySamp, nextMip->sampleBil(u, v), (sampSize-mipScale)/(nextMip->mipScale-mipScale));
		}
		else
		{
			return mySamp;
		}
	}

	Texture* getQuarterRes() const
	{
		Texture *qres = new Texture(width / 2, height / 2);
		qres->mipScale = mipScale * 2.0f;
		for(int y = 0; y < qres->height; ++y)
		{
			for(int x = 0; x < qres->width; ++x)
			{
				qres->setPixel(x, y, sampleBil((x * 2 + 0.5f) / (float)width, (y * 2 + 0.5f) / (float)height));
			}
		}
		return qres;
	}

	void generateMips()
	{
		if(width > 2 && height > 2)
		{
			nextMip = getQuarterRes();
			nextMip->generateMips();
		}
	}

	void Erode(float smoothness) {
		for (int i = 1; i < height - 1; i++) {
			for (int j = 1; j < width - 1; j++) {
				float d_max = 0.0f;
				int match[] = {0, 0};

				for (int u = -1; u <= 1; u++) {
					for (int v = -1; v <= 1; v++) {
						if (abs(u) + abs(v) > 0) {
							float d_i = pix[i * width + j].R - pix[(i + u) * width + j + v].R;
							if (d_i > d_max) {
								d_max = d_i;
								match[0] = u;
								match[1] = v;
							}
						}
					}
				}

				if (0 < d_max && d_max <= (smoothness / (float) width)) {
					float d_h = 0.5f * d_max;
					pix[i * width + j] -= d_h;
					pix[(i + match[0]) * width + j + match[1]] += d_h;
				}
			}
		}
	}

	void mapGradient(const VecRGB & col1, const VecRGB & col2)
	{
		int pixs = width * height;
		for(int i = 0; i < pixs; ++i)
			pix[i] = lerp(col1, col2, pix[i].R);
	}

	void subClamp(const float ammount, const float limit)
	{
		int pixs = width * height;
		for(int i = 0; i < pixs; ++i)
		{
			pix[i].R -= ammount; pix[i].G -= ammount; pix[i].B -= ammount;
			if(pix[i].R < limit) pix[i].R = limit;
			if(pix[i].G < limit) pix[i].G = limit;
			if(pix[i].B < limit) pix[i].B = limit;
		}
	}

	void clampToOne()
	{
		int pixs = width * height;
		for(int i = 0; i < pixs; ++i)
		{
			if(pix[i].R > 1.0f) pix[i].R = 1.0f;
			if(pix[i].G > 1.0f) pix[i].G = 1.0f;
			if(pix[i].B > 1.0f) pix[i].B = 1.0f;
		}
	}

	void generatePerlin(float CF)
	{
		float *aux = (float*)calloc(width * height, sizeof(float));
		float coef = 1.0f;
		if(pix != NULL) free(pix);
		pix = (VecRGB*)calloc(width * height, sizeof(VecRGB));
		int sx, sy;
		for (sx = 4, sy = width / 4; sx <= 512; sx *= 2, sy /= 2, coef *= CF)
		{
			for (int x = 0; x < (sx + 1) * (sx + 1); x++)
				aux[x % sx + (x / sx) * width] = (float) rand() / (float) RAND_MAX * 2.0f - 0.5f;
			for (int x = 0; x < sx; ++x)
				for (int y = 0; y < sx; ++y)
					for (int a = 0; a < sy; ++a)
						for (int b = 0; b < sy; ++b)
						{
							float ammount = (cerp(cerp(aux[y*width+x], aux[x + 1 + y*width], (float)(a * sx) / (float)width),
									cerp(aux[x + (y + 1) * width], aux[x + 1 + (y + 1)*width], (float)(a * sx) / (float)width), (float)(b * sx)/(float)width) - 0.5f) * coef;
							pix[x * sy + a + (y * sy + b) * height].R += ammount;
							pix[x * sy + a + (y * sy + b) * height].G += ammount;
							pix[x * sy + a + (y * sy + b) * height].B += ammount;
						}
		}
		//for(int y = 0; y < height; y++) for(int x = 0; x < width; ++x) pix[x+y*width].B=pix[x+y*width].G=pix[x+y*width].R;
		free(aux);
	}

	void generateWhiteNoise(float amplitude, float offset)
	{
		int pixs = width * height;
		for(int i = 0; i < pixs; ++i) pix[i].R = pix[i].G = pix[i].B = (float) rand() / (float) RAND_MAX * amplitude - offset;
	}

	void colorize(const VecRGB & color)
	{
		int pixs = width * height;
		for(int i = 0; i < pixs; ++i) pix[i].colorize(color);
	}

	float h(unsigned int x, unsigned int y) const
	{
		return pix[y * width + x].R;
	}

	Texture generateNormalMap() const
	{
		Texture nmap(width, height);
		float yScale = 1.0f, xzScale = 1.0f;

		for (int y = 0; y<height; ++y)
		{
		    for (int x = 0; x<width; ++x)
		    {
		        float sx = h(x<width-1 ? x+1 : x, y) - h(x>0 ? x-1 : x, y);
		        if (x == 0 || x == width-1)
		            sx *= 2;

		        float sy = h(x, y<height-1 ? y+1 : y) - h(x, y>0 ?  y-1 : y);
		        if (y == 0 || y == height -1)
		            sy *= 2;

		        nmap.pix[y*width+x].set(-sx*yScale, 2*xzScale, -sy*yScale);
		        nmap.pix[y*width+x].NormalizeIP();
		    }
		}

		return nmap;
	}

	void saveBMP(const char * fileName) const
	{
		FILE *f = fopen(fileName, "wb");
		int padding = (4 - (width * 3) % 4) % 4;

		unsigned char hdr[] = {'B','M','6','0','*',0,0,0,0,0,'6',0,0,0,'(',0,0,0,0,5,0,0,0xD0,2,0,0,1,0,24,0,0,0,0,0,0,'0','*',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
		hdr[0x12 + 0] = width & 255;
		hdr[0x12 + 1] = (width >> 8) & 255;
		hdr[0x12 + 2] = (width >> 16) & 255;
		hdr[0x12 + 3] = (width >> 24) & 255;

		hdr[0x16 + 0] = height & 255;
		hdr[0x16 + 1] = (height >> 8) & 255;
		hdr[0x16 + 2] = (height >> 16) & 255;
		hdr[0x16 + 3] = (height >> 24) & 255;

		fwrite(hdr, 1, 54, f);
		for(int y = height - 1; y >= 0; y--)
		{
			for(int x = 0; x < width; x++)
			{
				VecRGB rgb = getPixel(x, y);
				int cR = rgb.R * 256;
				int cG = rgb.G * 256;
				int cB = rgb.B * 256;
				if(cR < 0) cR = 0; if(cR > 255) cR = 255;
				if(cG < 0) cG = 0; if(cG > 255) cG = 255;
				if(cB < 0) cB = 0; if(cB > 255) cB = 255;
				fputc((unsigned char)cB, f);
				fputc((unsigned char)cG, f);
				fputc((unsigned char)cR, f);
			}

			for(int i = 0; i < padding; i++)
				fputc('\0', f);
		}
		fclose(f);
	}

	void scale(float s)
	{
		int ntex = width * height;
		for(int i = ntex - 1; i >= 0; --i) pix[i] *= s;
	}

	void offset(float s)
	{
		int ntex = width * height;
		for(int i = ntex - 1; i >= 0; --i) pix[i] += s;
	}

	void normalize()
	{
		int ntex = width * height;
		float min, max, diff, scale;
		min = max = pix[0].R;
		for(int i = ntex - 1; i >= 0; --i)
		{
			if(pix[i].R < min) min = pix[i].R;
			if(pix[i].G < min) min = pix[i].G;
			if(pix[i].B < min) min = pix[i].B;
			if(pix[i].R > max) max = pix[i].R;
			if(pix[i].G > max) max = pix[i].G;
			if(pix[i].B > max) max = pix[i].B;
		}

		diff = max - min;
		scale = 1.0f / diff;

		for(int i = ntex - 1; i >= 0; --i) { pix[i] -= min; pix[i] *= scale; }
	}

	void freeMips()
	{
		if(nextMip != NULL)
		{
			nextMip->freeMips();
			delete nextMip;
		}
	}

	~Texture()
	{
		//if(nextMip != NULL) delete nextMip;
		//if(pix != NULL) free(pix);
	}
};

#endif /* TEXTURE_H_ */
